#include "Lib/network.h"
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <complex>
#include <iostream>
#include <fstream>
#include <sstream>
#include <math.h> 
#include <vector>
#include <algorithm>

#define DEBUG 1



using namespace std;

void read_Stream(char *in_File, ifstream *in_Stream){
	in_Stream = new ifstream(in_File);
	while(in_Stream->fail()){
		cout<<"Opening failed. Please try again.\n";
		cin>>in_File;
		
		in_Stream->open(in_File);
	}
}

int get_Size(ifstream *in_Stream){
	char *buffer;
	int length, size;

	if(!in_Stream->is_open()){
		cout<<"ANN.cpp::get_Size Error: Input file not open.\n";
		return 0;
	}
		
	in_Stream->seekg(0,ios::end);
	length = in_Stream->tellg();	
	in_Stream->seekg(0,ios::beg);
	
	// Allocate buffer memory //
	buffer = new char[length];
	in_Stream->read(buffer,length);
	
	size = 0;
	while(*buffer != '.'){
		while(*buffer != '\n'){
			buffer++;
		}
		buffer++;
		size++;
	}
	return size;
}

bool extract(ifstream *in_Stream, double **in_Vect, double **desired_Out_Vect, int in_Neurons, int out_Neurons){
	char *buffer;
	int length, count;

	if(!in_Stream->is_open()){
		cout<<"ANN.cpp::Extract Error: Input file not open.\n";
		return false;
	}
		
	in_Stream->seekg(0,ios::end);
	length = in_Stream->tellg();	
	in_Stream->seekg(0,ios::beg);
	
	// Allocate buffer memory //
	buffer = new char[length];
	in_Stream->read(buffer,length);
	
	
	count = 0;
	while(*buffer != '.'){
		for(int n = 0; n < in_Neurons; n++){
			in_Vect[count][n] = atof(buffer);
			while(*buffer != '\t') buffer++;
			buffer++;
		}
		for(int m = 0; m < out_Neurons; m++){
			desired_Out_Vect[count][m] = atof(buffer);
			if(m != out_Neurons - 1){
	
				while(*buffer != '\t') buffer++;
				buffer++;
			}else{
	
				while(*buffer != '\n') buffer++;
				buffer++;
			}		
		}
		count++;
	}
	return true;
}

double net_Error(double *out_Vect, double *desired_Out_Vect, int out_Neurons){
	double error = 0;
	for(int n = 0; n < out_Neurons; n++){
		error += fabs(out_Vect[n] - desired_Out_Vect[n]);
	}
	
	return (error / (out_Neurons));
}

int  main(int argc, char *argv[]){

	char comm;
	char *in_File = new char[100];
	bool net_Created, data_Read, net_Trained = false;


	int layers_Number = 0;
	int func = 1;
	double alpha,lambda;

	int epoch, size;
	vector<int> indices;
	int max_Epoch = 10000;
	double Error;

	double **in_Vect;
	double **desired_Out_Vect;
	double *out_Vect;
	double error;
	int *neurons_Per_Layer;

	class Network *network = 0;
	ifstream *in_Stream;
	ofstream *out_Stream;

	while(1){

		cout<<"CNN>";
		cin>>comm;
	
		switch(comm){
			case 'Q':
				exit(0);
			case 'C':
				cout<<"Network Creation\n";
				
				while(layers_Number < 2){
					cout<<"Please enter the number of layers (min: 2) : ";
					cin>>layers_Number;
				}
				neurons_Per_Layer = new int[layers_Number];
				
				cout<<"Please enter the number of Input Nodes: ";
				cin>>neurons_Per_Layer[0];

				for(int i = 1; i < layers_Number-1; i++){
					cout<<"Please enter the number of nodes in hidden layer #"<<i<<": ";
					cin>>neurons_Per_Layer[i];
				}

				cout<<"Please enter the number of Output Nodes: ";
				cin>>neurons_Per_Layer[layers_Number-1];
				out_Vect = new double[neurons_Per_Layer[layers_Number - 1]];
					
				cout<<"Please enter the function #:\n";
				cout<<"#0 : Signum function\n";
				cout<<"#1 : Logistic function\n";
				cout<<"#2 : TanH function\n";
				cin>>func;

				cout<<"Please enter Lambda (learning coef) [0-1]: ";
				cin>>lambda;
				cout<<"Please enter Alpha (momentum coef) [0-1]: ";
				cin>>alpha;

				cout<<"Creation...\n";
				network = new Network(layers_Number, neurons_Per_Layer);
				cout<<"Initialization...\n";
				network->init(func);
				network->randomize();
				cout<<"Setting parameters...\n";
				network->set_Lambda(lambda);
				network->set_Alpha(alpha);
				cout<<"Network created.\n";
				
				net_Created = true;

				break;
			case 'L':
				if(!net_Created){
					cout<<"Please create a network first.\n";
					cout<<"Type H for help.\n";
					break;
				}			
	
				cout<<"Please enter the Input File: ";
				cin>>in_File;
				
				in_Stream = new ifstream(in_File);
				size = get_Size(in_Stream);

				for(int s = 0; s < size; s++)indices.push_back(s);

				in_Vect = new double *[size];
				for(int i = 0; i < size; i++){
					in_Vect[i] = new double[neurons_Per_Layer[0]];
				}
				desired_Out_Vect = new double *[size];
				for(int i = 0; i < size; i++){
					desired_Out_Vect[i] = new double[neurons_Per_Layer[layers_Number-1]];
				}	
				data_Read = extract(in_Stream, in_Vect, desired_Out_Vect, neurons_Per_Layer[0], neurons_Per_Layer[layers_Number-1]);
				
				break;

			case 'T':
				if(!net_Created){
					cout<<"Please create a network first.\n";
					cout<<"Type H for help.\n";
					break;
				}
				if(!data_Read){
					cout<<"Please load an input file first.\n";
					cout<<"Type H for help.\n";
					break;
				}
				
				cout<<"Please enter the error threshold: [0-1]";
				cin>>error;

				out_Stream = new ofstream("train_log.txt");
			
				// Generating output //	
				*out_Stream<<"Training network.\nTraining set : "<<in_File<<" containing "<<size<<" examples."<<endl;
				*out_Stream<<layers_Number<<" layers.\nInput Layer: "<<neurons_Per_Layer[0]<<" nodes."<<endl;
				for(int i = 1; i < layers_Number - 1; i++) *out_Stream<<"Hidden layer #"<<i<<": "<<neurons_Per_Layer[i]<<" nodes."<<endl;
				*out_Stream<<"Output layer: "<<neurons_Per_Layer[layers_Number - 1]<<" nodes"<<endl;
				*out_Stream<<"Lambda: "<<lambda<<"; Alpha: "<<alpha<<"; Error threshold: "<<error<<endl;		
				// end of output //

				epoch = 0;
				// Randomizing entries //
					random_shuffle( indices.begin(), indices.end() );


				while(epoch < max_Epoch){
					Error = 0;
					*out_Stream<<epoch;
					
					for(int s = 0; s < size; s++){
						network->train(in_Vect[indices[s]], out_Vect, desired_Out_Vect[indices[s]], error);
					
						cout<<"epoch: "<<epoch<<" example: "<<s<<endl;
						//for(int i = 0;i<neurons_Per_Layer[layers_Number - 1];i++) cout<<"n "<<i<<" "<<out_Vect[i]<<"/"<<desired_Out_Vect[s][i]<<" ";
						//cout<<endl;
					//	cout<<"net error "<<net_Error(out_Vect, desired_Out_Vect[s], neurons_Per_Layer[layers_Number - 1])<<endl;
						
						Error += net_Error(out_Vect, desired_Out_Vect[s], neurons_Per_Layer[layers_Number - 1]);
					}
					//cout<<"error / size"<<Error / size<<endl;
					*out_Stream<<"\t"<<Error / size<<endl;
					if((Error / size) < error){
						//for(int i = 0;i<neurons_Per_Layer[layers_Number - 1];i++) cout<<"n "<<i<<" "<<out_Vect[i]<<"/"<<desired_Out_Vect[layers_Number - 1][i]<<" ";
						
						break;
					}
					epoch++;
				}

				if(epoch < max_Epoch){
					*out_Stream<<"Network trained in "<<epoch<<" steps. Final error : "<<Error/size<<endl;
					cout<<"Network trained.\n";
						
					net_Trained = true;
				}else{
					*out_Stream<<"Training unsuccessful"<<endl;
					cout<<"Training failed: too many steps.\n";
					
					net_Trained = false;
				}
				break;

			// auto training for parameters testing //
			// WARNING : CAN BE VERY LONG IF TOO MANY VARIATIONS OF PARAMETERS //
			case 'A':
				
				int layers_Min, layers_Max, in_Nodes, out_Nodes, h_Layers_Min, h_Layers_Max, func;
				double lambda_Min, lambda_Max, lambda_Step, alpha_Min, alpha_Max, alpha_Step;

				if(net_Created){
					delete network;
				}
				
				cout<<"Network Creation\n";
				
				while(layers_Number < 2){
					cout<<"Please enter the number of layers (min: 2) : ";
					cin>>layers_Number;
				}
				neurons_Per_Layer = new int[layers_Number];
				
				cout<<"Please enter the number of Input Nodes: ";
				cin>>neurons_Per_Layer[0];

				for(int i = 1; i < layers_Number-1; i++){
					cout<<"Please enter the number of nodes in hidden layer #"<<i<<": ";
					cin>>neurons_Per_Layer[i];
				}

				cout<<"Please enter the number of Output Nodes: ";
				cin>>neurons_Per_Layer[layers_Number-1];
				out_Vect = new double[neurons_Per_Layer[layers_Number - 1]];
					
				cout<<"Please enter the function #:\n";
				cout<<"#0 : Signum function\n";
				cout<<"#1 : Logistic function\n";
				cout<<"#2 : TanH function\n";
				cin>>func;


			
				cout<<"Please enter the range for Lambda:\nmin: ";
				cin>>lambda_Min;
				cout<<"max: ";
				cin>>lambda_Max;

				cout<<"Please enter the steps size for Lambda: ";
				cin>>lambda_Step;				

				cout<<"Please enter the range for Alpha:\nmin: ";
				cin>>alpha_Min;
				cout<<"max: ";
				cin>>alpha_Max;
				
				cout<<"Please enter the steps size for Alpha: ";
				cin>>alpha_Step;
			
				cout<<"Please enter the error threshold: [0-1]";
				cin>>error;

				cout<<"Please enter the Input File: ";
				cin>>in_File;
			
				// LOAD //	
				in_Stream = new ifstream(in_File);
				size = get_Size(in_Stream);

				in_Vect = new double *[size];
				for(int i = 0; i < size; i++){
					in_Vect[i] = new double[neurons_Per_Layer[0]];
				}
				desired_Out_Vect = new double *[size];
				for(int i = 0; i < size; i++){
					desired_Out_Vect[i] = new double[neurons_Per_Layer[layers_Number-1]];
				}	
				data_Read = extract(in_Stream, in_Vect, desired_Out_Vect, neurons_Per_Layer[0], neurons_Per_Layer[layers_Number - 1]);
				
				cout<<"Please enter the output file: ";
				cin>>in_File;	
				out_Stream = new ofstream(in_File);			
	
				// END LOAD //				
				
				*out_Stream<<"Auto-training output. format :\tlambda\talpha\tepochs\tError"<<endl;; 

				*out_Stream<<layers_Number<<"\t";
				
				for(lambda = lambda_Min; lambda < lambda_Max; lambda += lambda_Step){
					*out_Stream<<lambda<<"\t";
					for(alpha = alpha_Min; alpha < alpha_Max; alpha += alpha_Step){
						*out_Stream<<alpha<<"\t";
						// CREATE //
						if(net_Created){
							delete network;
						}
						network = new Network(layers_Number, neurons_Per_Layer);
						network->init(func);
						network->randomize();
						network->set_Lambda(lambda);
						network->set_Alpha(alpha);
						net_Created = true;

						// END CREATE //

						// TRAIN //
						
						int count = 0;
						epoch = 0;
							
						while(epoch < max_Epoch){
							Error = 0;
							for(int s = 0; s < size; s++){
								if(network->train(in_Vect[s], out_Vect, desired_Out_Vect[s], error)){
									Error += net_Error(out_Vect, desired_Out_Vect[s], neurons_Per_Layer[layers_Number - 1]);
								}	
							}
							if((Error / size) < error){
								break;
							}
							epoch++;
							count ++;
							if(count == 20){
								cout<<epoch<<endl;
								count = 0;
							}
						}
							
						*out_Stream<<epoch<<"\t"<<Error/size<<endl;

						if(epoch < max_Epoch){
							cout<<"Network trained"<<endl;
							net_Trained = true;
						}else{
							net_Trained = false;
						}
						// END TRAIN //	

					}
				}							
			
				break;
			case 'E':
				if(!net_Created){
					cout<<"Please create a network first.\n";
					cout<<"Type H for help.\n";
					break;
				}
				if(!data_Read){
					cout<<"Please load an input file first.\n";
					cout<<"Type H for help.\n";
					break;
				}
				if(!net_Trained){
					cout<<"Please train the network first.\n";
					cout<<"Type H for help.\n";
					break;
				}
					
				out_Stream = new ofstream("eval_log.txt");
				
				// Generating output //
				*out_Stream<<"Evaluating.\nEvaluating set: "<<in_File<<" containing "<<size<<" examples."<<endl;
				*out_Stream<<layers_Number<<" layers.\nInput Layer: "<<neurons_Per_Layer[0]<<" nodes."<<endl;
				for(int i = 0; i < layers_Number - 2; i++) *out_Stream<<"Hidden layer #"<<i<<": "<<neurons_Per_Layer[i]<<" nodes."<<endl;
				*out_Stream<<"Output layer: "<<neurons_Per_Layer[layers_Number - 1]<<" nodes"<<endl;
				*out_Stream<<"Lambda: "<<lambda<<"; Alpha: "<<alpha<<endl;		
				// end of output //

				for(int s = 0; s < size; s++){
					network->run(in_Vect[s], out_Vect);
					
					// Generating output //
					*out_Stream<<s<<"\t";
					for(int i = 0;i<neurons_Per_Layer[layers_Number - 1];i++) *out_Stream<<out_Vect[i]<<"\t"<<desired_Out_Vect[s][i]<<"\t";
					*out_Stream<<net_Error(out_Vect, desired_Out_Vect[s], neurons_Per_Layer[layers_Number - 1])<<endl;
					// End of output //
				}
				break;
			case 'H':
				cout<<"ANN help"<<endl;
				cout<<"Possible commands :\n\tC: create a new network\n\tL: load an input file\n\tT: train a network with a loaded training set file\n\tE: evaluates a loaded file\n\tR: runs the network on a loaded file\n\tQ: exit the program\n\tH: this help"<<endl;
	
			default:
				cout<<"Type H for help\n";
				break;
		}
	}

return 0;

}
